﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.IO;

namespace FAfx.UnitTesting
{
    /// <summary>
    /// Our version of VS2008 doesn't have MSTest, but VS2012 does
    /// This console app runs all methods marked as [TestMethod] in classes marked as [TestClass]
    /// and will pass/fail in a console app
    /// This is designed to be CUT/PASTE the test classes to migrate to MSTest projects
    /// </summary>
    /// <remarks>
    /// This is some old code I had for when I was on VS2008Express; cool eh?
    /// (Its had NO testing for threads etc)
    /// </remarks>
    class Program
    {
        /// <summary>
        /// TDD for when you aint got TDD
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            WriteLine(ConsoleColor.Green, "-------------------------------");
            WriteLine(ConsoleColor.Green, "UnitTesting");
            WriteLine(ConsoleColor.Green, "-------------------------------");

            var testMethods = GetTestMethods();

            var testResults = (
                from x in testMethods
                select GetTestResults(x)
                ).ToList();

            Console.WriteLine();

            WriteLine(ConsoleColor.Green, "Passes: {0}", testResults.FindAll(x => x == TestResult.Pass).Count());
            WriteLine(ConsoleColor.Red, "Fails: {0}", testResults.FindAll(x => x == TestResult.Fail).Count());
            WriteLine(ConsoleColor.Yellow, "Exceptions: {0}", testResults.FindAll(x => x == TestResult.Exception).Count());

            Console.ReadKey();
        }

        #region Private Classes/Enums

        private class TestAttributes
        {
            internal Type TestClass { get; set; }
            internal MethodInfo TestMethod { get; set; }
        }

        private enum TestResult
        {
            Pass,
            Fail,
            Exception
        }

        #endregion

        #region Private Methods

        private static List<TestAttributes> GetTestMethods()
        {
            var testTypes =
                from a in AppDomain.CurrentDomain.GetAssemblies()
                from t in a.GetTypes()
                let attributes = t.GetCustomAttributes(typeof(TestClassAttribute), true)
                where attributes != null && attributes.Length > 0
                select t;

            var testMethods =
                from t in testTypes
                from m in t.GetMethods()
                let attributes = m.GetCustomAttributes(typeof(TestMethodAttribute), true)
                where attributes != null && attributes.Length > 0
                select new TestAttributes() { TestClass = t, TestMethod = m };

            return testMethods.ToList();
        }

        private static TestResult GetTestResults(TestAttributes tm)
        {
            try
            {
                try
                {
                    // This is ugly, but need to suppress the output for other code using console.writeline
                    Console.SetOut(TextWriter.Null);
                    tm.TestMethod.Invoke(Activator.CreateInstance(tm.TestClass), null);
                }
                finally
                {
                    Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true });
                }

                WriteLine(ConsoleColor.DarkGreen, "Pass: {0} > {1}", tm.TestClass.Name, tm.TestMethod.Name);
                return TestResult.Pass;

            }
            catch (TargetInvocationException TIEx)
            {
                if (TIEx.InnerException is TestException)
                {
                    WriteLine(ConsoleColor.Red, "Fail: {0} > {1} - {2}", tm.TestClass.Name, tm.TestMethod.Name, TIEx.InnerException.Message);
                    return TestResult.Fail;
                }
                else
                {
                    WriteLine(ConsoleColor.Yellow, "Exception: {0} > {1} - {2}", tm.TestClass.Name, tm.TestMethod.Name, TIEx.InnerException);
                    return TestResult.Exception;
                }
            }
        }

        private static void WriteLine(ConsoleColor color, string format, params object[] args)
        {
            Console.ForegroundColor = color;
            Console.WriteLine(format, args);
            Console.ResetColor();
        }

        #endregion

    }

    [AttributeUsage(AttributeTargets.Class)]
    internal class TestClassAttribute : System.Attribute { }
    [AttributeUsage(AttributeTargets.Method)]
    internal class TestMethodAttribute : System.Attribute { }

    [Serializable]
    internal abstract class TestException : Exception
    {
        internal TestException(string message) : base(message) { }
    }
    [Serializable]
    internal sealed class NotEqualException : TestException
    {
        internal NotEqualException(string message) : base(message) { }
    }

    internal static class Assert
    {
        internal static void AreEqual<T>(T expected, T actual)
        {
            if (actual.Equals(expected)) return;
            //When it reaches here, you may as well disable the 'stop at this exception'
            throw new NotEqualException(string.Format("actual <{0}> does not equal expected <{1}>", actual, expected));
        }
    }
}



